﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.IO.Ports;




namespace Communication
{

    class LineSerialPort
    {

        #region Variables and enumerations
            public System.IO.Ports.SerialPort serialComPort;

            private Int32 PortStateFlag;
            public enum CodeOfLineParameter : byte 
            {   
                LINE_POSSITION      = 0, /* Center of line position. */
                LINE_RIGHT_POSITION = 1, /* Right position of line. */
                LINE_LEFT_POSSITION = 2,  /* Left position of line. */
                LINE_WIDTH          = 3
            };
            CodeOfLineParameter Line_Data_Type;
                /* INT32 union structure. 
                 * We will use it to save received from serial line value.
                 */
                [StructLayout(LayoutKind.Explicit)]
                public struct Union_int32
                {   
                    /* INT 32 
                     * Here we should define place of union elements exactly
                     * to be sure in right position of each byte during serialisation.
                     */
                    [FieldOffset(0)]
                    public Int32 i32;
                    [FieldOffset(0)]
                    public byte b0;
                    [FieldOffset(1)]
                    public byte b1;
                    [FieldOffset(2)]
                    public byte b2;
                    [FieldOffset(3)]
                    public byte b3;
                };

                Int32 Line_possition;
                Int32 Line_width;
        
                #region Round buffer
                /* Receiving round buffer. */
                private byte[] round_rx_buf;
                /* Head index of round buffer. */
                private int round_rx_buf_head;
                /* Tail index of round buffer. */
                private int round_rx_buf_tail;
                /* Size of bytes stored in round buffer. */
                private int round_rx_buf_size;
                #endregion

        #endregion

        #region Delegate and callback function.
                /* This will be pointer to callback function.
                 * Whan we will create new element of class we should send it name of function that it will 
                 * call like callback function.
                 */
                public delegate void MyCallDelegate(double x, double y, double z);
                /* Function that we will call inside class. */
                public MyCallDelegate CallDrawGraphFunction; 
        #endregion


                #region Properties
                /* Round buffer size. */
            /* TODO: set value like input value. */
            uint round_buffer_size;
        public  double read_byte_counter = 0;

        #endregion

        #region Constructors

            public LineSerialPort(MyCallDelegate d) /* Here whan we are creating new element we are send pointer to callback function. */
            {
                   CallDrawGraphFunction = d;
            }

            public int ComPort(
                                string PortName,
                                System.IO.Ports.StopBits Stop_Bit,
                                byte DataBits,
                                int BaudRate,
                                System.IO.Ports.Parity Parity,
                                uint BufferSize
                              ) 
            {
                /* Create new serial communication port component
                 * to use it for receive and transmit data.
                 */
                round_buffer_size = BufferSize;
                serialComPort = new System.IO.Ports.SerialPort();
                /* Create round buffer for saving RX data from serial line. */
                round_rx_buf = new byte[round_buffer_size];

                /* Set init value of round buffer variables. */
                round_rx_buf_head = 0;
                round_rx_buf_tail = 0;
                round_rx_buf_size = 0;

                /* Opening Serial communication port */
                try
                {
                    if (PortName == "")
                    {
                        /* "PortName" error. */
                        MessageBox.Show("Invalid serial potr name.");
                        return 1;
                    }
                    // Setup port parameters.
                    serialComPort.StopBits = Stop_Bit;
                    serialComPort.DataBits = DataBits;
                    serialComPort.BaudRate = BaudRate;
                    serialComPort.PortName = PortName;
                    serialComPort.Parity = Parity.None;
                    /* Add  DataReceived event function. */
                    serialComPort.DataReceived += new SerialDataReceivedEventHandler(SerialPortEventDataReceived);
                    // Opening port.
                    serialComPort.Open();
                    if (serialComPort.IsOpen)
                    {
                        // Port opened OK.
                        PortStateFlag = 1;
                        return 0;
                    }
                }
                catch (Exception e1)
                {
                    MessageBox.Show(e1.Message);
                    // Some error happend.
                    return 2;
                }
                // Just return :) .
                return 3;
            }
            public int ComPortClose(  )
            {
                      // Port opened OK.
                      PortStateFlag = 0;
                      return 0;
            }
        #endregion

        #region Component events
            /*  Read byte from RX to RX round buffer. */
            private void SerialPortEventDataReceived(object sender, SerialDataReceivedEventArgs e)
            {
                int BytesToRead;

                /* Copy data from serial RX buffer to round buffer. */
                if (PortStateFlag == 1)
                {
                    BytesToRead = ReadDataFromSerialRxBuffer();
                    ParsingRoundBuffer();
                }
                else
                { 
                  serialComPort.Close(); 
                }
            }


           /* Parsing received data. 
            * Protocol depended function.
            */
            private int ParsingRoundBuffer()
            {
                int ReadByte = 0;
                int temp_size, temp_head;
                Byte Line_CRC = 0;
                Union_int32 Line_value;

                Line_value.i32 = 0;
               /* Save current round buffer state.
                * It allow us to make rollback.
                */
                temp_size = round_rx_buf_size;
                temp_head = round_rx_buf_head;
                while (round_rx_buf_size > 1)
                {

                    ReadByte = ReadByteFromRoundBuffer();
                    /* Looking for HEADER of packet. */
                    if (ReadByte == 0x73)/* HEADER of packet should be 0x73. */
                    {
                        int size = ReadByteFromRoundBuffer();/* For interferometer packet we use fixed value 0x05. */

                        if (size == 0x05)
                        {
                            if (round_rx_buf_size < (size + 1))/* 1 is size of CRC. */
                            {
                                /* If amount of byte in round buffer less than size of packet
                                 * set position and size of it to start value and return from function.
                                 * Finally next ReceiveByte event will add more byte to buffer and we will try 
                                 * decode packet again.
                                 */
                                round_rx_buf_size = temp_size;
                                round_rx_buf_head = temp_head;
                                return (2);
                            }
                            Line_Data_Type = (CodeOfLineParameter)ReadByteFromRoundBuffer();/* Read code of data type. */
                            Line_value.b0  = (Byte)ReadByteFromRoundBuffer(); /* Read LSB of 32 bits value. */
                            Line_value.b1  = (Byte)ReadByteFromRoundBuffer();
                            Line_value.b2  = (Byte)ReadByteFromRoundBuffer();
                            Line_value.b3  = (Byte)ReadByteFromRoundBuffer(); /* Read MSB of 32 bits value. */
                            Line_CRC       = (Byte)ReadByteFromRoundBuffer();      /* Read packet CRC. */
                            Byte Current_CRC = (Byte)(0xFF - (Byte)(
                                                        Line_Data_Type +
                                                        Line_value.b0 +
                                                        Line_value.b1 +
                                                        Line_value.b2 +
                                                        Line_value.b3
                                                      ));
                            if (Current_CRC != Line_CRC)
                            {
                                CallDrawGraphFunction(read_byte_counter, 0,0);
                                read_byte_counter++;
                            }
                            else
                            {
                                if (Line_Data_Type == CodeOfLineParameter.LINE_POSSITION) 
                                {
                                    Line_possition= Line_value.i32;
                                }
                                if (Line_Data_Type == CodeOfLineParameter.LINE_WIDTH)
                                {
                                  //  if ( (Line_value.i32 > 0) && (  Math.Abs(Line_value.i32) < 320) )
                                  //  {
                                        Line_width = Line_value.i32;
                                  //  }
                                }
                                CallDrawGraphFunction(read_byte_counter, (double)Line_possition,(double) Line_width);
                                read_byte_counter++;
                            }
                        }
                    }
                }
                return 1;
            }


           /* Read byte from Round buffer. Function return Byte value 
            * or -1 if Round buffer is empty. 
            */
            private int ReadByteFromRoundBuffer()
            {
                int ReadByte = 0;
                if (round_rx_buf_size > 0)
                {
                    /* Read byte from serial round buffer */
                    ReadByte = (int)round_rx_buf[round_rx_buf_head];
                    /* Dicrease size of byte stored in round buffer( byte counter ). */
                    round_rx_buf_size--;
                    /* Increase pounter to the head of round buffer */
                    round_rx_buf_head++;
                    /* Check if pointer reach end of buffer */
                    if (round_rx_buf_head >= round_buffer_size)
                    {
                        /* Round the pointer */
                        round_rx_buf_head = 0;
                    }
                    return ReadByte;
                }
                else
                {
                    return (-1); /* Round buffer is empty. */
                }
            }

           /* This function just copy all received data from serial received buffer to 
            * Round buffer. We can use that data later reading it from Round buffer.
            */
            private int ReadDataFromSerialRxBuffer()
            {
                int BytesToRead;

                /* Check how match bytes in buffer. */
                    BytesToRead = serialComPort.BytesToRead;

                    /* Read all bytes from Serial port buffer. */
                    for (int count = 0; count < BytesToRead; count++)
                    {
                        /* This version of buffer read buffer size and drop all another bytes
                         * before it will be read.
                         */
                        if (round_rx_buf_size < round_buffer_size)
                        {
                            /* Copy byte to the end of round buffer */
                            round_rx_buf[round_rx_buf_tail] = (Byte)serialComPort.ReadByte();
                            /* Increase size of byte stored in round buffer. */
                            round_rx_buf_size++;
                            /* Increase tail-pounter of round buffer */
                            round_rx_buf_tail++;
                            /* Check if pointer reach end of buffer  */
                            if (round_rx_buf_tail >= round_buffer_size)
                            {
                                /* Round the pointer */
                                round_rx_buf_tail = 0;
                            }
                        }
                    }
                    return BytesToRead;
            }
        #endregion
    }
}
